// @formatter:off
export enum Content {
  start    = 0b000,
  end      = 0b001,
  center   = 0b010,
  around   = 0b011,
  between  = 0b100,
  evenly   = 0b111,
}
export type ContentString = 'start' | 'end' | 'center' | 'between' | 'around' | 'evenly'

export enum Items {
  auto     = 0b000_000,
  start    = 0b001_000,
  center   = 0b010_000,
  end      = 0b100_000,
  stretch  = 0b111_000,
}
export type ItemsString = 'start' | 'end' | 'center' | 'auto' | 'stretch'

export enum AlignItems {
  start    = 0b000_000,
  center   = 0b010_000,
  end      = 0b100_000,
  baseline = 0b001_000,
  stretch  = 0b111_000,
}
export type AlignItemsString = 'start' | 'end' | 'center' | 'baseline' | 'stretch'

export enum Self {
  auto     = 0b000_000_000,
  start    = 0b001_000_000,
  center   = 0b010_000_000,
  end      = 0b100_000_000,
  stretch  = 0b111_000_000,
}
export type SelfString = 'start' | 'end' | 'center' | 'auto' | 'stretch'
// @formatter:on

export type JustifyString = `${ContentString | ''}${`-${ItemsString}` | ''}${`-${SelfString}` | ''}`
export type JustifyNumber = Content | Items | Self;
export type JustifyInput = JustifyString | JustifyNumber | undefined | null;

export type AlignString = `${ContentString | ''}${`-${AlignItemsString}` | ''}${`-${SelfString}` | ''}`
export type AlignNumber = Content | AlignItems | Self;
export type AlignInput = AlignString | AlignNumber | undefined | null;

export enum CISMode {
  align = 1
}

const getEnum = (i: number, mode?: CISMode): typeof Content | typeof Items | typeof Self | typeof AlignItems => {
  if (i === 0) return Content;
  if (i === 1) {
    if (mode === CISMode.align) return AlignItems;
    return Items;
  }
  return Self;
};

const detectCISKey = (v: number | string | undefined | null, i: number, mode?: CISMode): string | undefined => {
  if (v == null) return undefined;
  const obj = getEnum(i, mode);
  if (v in obj) {
    if (typeof v === 'number') {
      return obj[v];
    }
    return v;
  }
  return undefined;
};


const extractCISBinary = (v: number): [number, number, number] =>
  [(v & Content.evenly), (v & Items.stretch), (v & Self.stretch)];

const extractCIS = (input: string | AlignInput | JustifyInput): (number | string | undefined | null)[] => {
  if (input != null && input !== '') {
    switch (typeof input) {
      case 'number' :
        return extractCISBinary(input);
      case 'string' :
        return input.split(/[-]/).map(v => v.trim());
    }
  }
  return [];
};

export type CISParseResult = {
  content?: string | undefined,
  items?: string | undefined,
  self?: string | undefined,
}

export const parseCIS = (v: string | AlignInput | JustifyInput, mode?: CISMode): CISParseResult => {
  const values = extractCIS(v);
  return {
    content: detectCISKey(values[0] ?? undefined, 0, mode),
    items  : detectCISKey(values[1] ?? undefined, 1, mode),
    self   : detectCISKey(values[2] ?? undefined, 2, mode),
  };
};

// @formatter:off
export enum Flex {
  row     = 0b00_00,
  col     = 0b00_01,
  rowRev  = 0b00_10,
  colRev  = 0b00_11,
  nowrap  = 0b01_00,
  wrap    = 0b10_00,
  wrapRev = 0b11_00,
}
// @formatter:on

export type DirString = 'row' | 'col' | 'rowRev' | 'colRev';
export type WrapString = 'nowrap' | 'wrap' | 'wrapRev';
type FlexKey = DirString | WrapString;

export type FlexFlowString = `${DirString | ''}${`-${WrapString}` | ''}`;
export type FlexFlowInput = FlexFlowString | Flex | undefined | null;

export type FlexFlowParsed = {
  dir: Flex.row | Flex.col | Flex.rowRev | Flex.colRev,
  wrap: Flex.nowrap | Flex.wrap | Flex.wrapRev | 0, // 在不指定 wrap 值的时候，是可能为 0 的
}

const defaultFlex = (): FlexFlowParsed => ({ dir: Flex.row, wrap: 0 });

const parseFlowBinary = (v: number): FlexFlowParsed => {
  return { dir: v & Flex.colRev, wrap: v & Flex.wrapRev };
};

const isFlexKey = (v: string): v is FlexKey => v in Flex && typeof Flex[v as FlexKey] === 'number';

const parseFlowString = (s: FlexFlowString): FlexFlowParsed => {
  let res = 0;
  s.split(/[-]/).slice(0, 2).forEach((v) => {
    if (isFlexKey(v)) {
      res |= Flex[v];
    }
  });
  return parseFlowBinary(res);
};

export const parseFlow = (v: FlexFlowInput): FlexFlowParsed => {
  // 过滤掉白值
  if (v != null && v !== '' && v !== 0) {
    switch (typeof v) {
      case 'number':
        return parseFlowBinary(v);
      case 'string':
        return parseFlowString(v);
    }
  }
  return defaultFlex();
};

// export type UnitValue = number | `${number}${'em' | 'px' | 'rpx' | 'rem' | '%' | ''}` | null | undefined;
//
// export type GapInput = UnitValue | (UnitValue)[];
//
// export type GapProps = {
//   gap?: GapInput,
//   gapX?: GapInput | null,
//   gapY?: GapInput | null,
// }
//
// export const extractGap = (gap?: GapInput | null): GapProps | void => {
//   if (gap == null) return;
//   switch (typeof gap) {
//     case 'string' :
//     case 'number' :
//       return { gap };
//     default :
//       if (Array.isArray(gap) && gap.length > 0) {
//         const [gapX, gapY] = gap;
//         if (gapX === gapY) {
//           return { gap: gapX };
//         }
//         return { gapX, gapY };
//       }
//   }
// };

export type FlexProps = {
  flow?: FlexFlowInput,
  justify?: JustifyInput,
  align?: AlignInput,
}

/**
 * 生成的 flex 基础属性
 *
 * 主要用于给 JSX 标签中属性声明，因为 React 标签属性声明，是会被反复执行的，所以此方法尽可能减少处理过程。
 *
 * @example
 *
 * ```tsx
 * <View flex={flex('col', 'between')}>
 *   {children}
 * </View>
 * ```
 *
 * 该方法只返回浅层结构（一层）的数据内容，以便于做 shallow compare
 *
 * @param flow - flex flow 声明
 * @param justify - justify 声明
 * @param align - align 声明
 */
export const flex = (
  flow?: FlexFlowInput | false,
  justify?: JustifyInput,
  align?: AlignInput,
): FlexProps | undefined => {
  if (flow === false) return undefined;
  return { flow, justify, align };
};

export type FlexParsed = {
  justify: CISParseResult,
  align: CISParseResult,
} & FlexFlowParsed

/**
 * 根据 Flex 基础属性解析出所需的 Flex 特性
 *
 * 因不同的平台，对 Flex 结果，所需要进行的处理不同，HTML或小程序，可以生成 ClassName ，而 Native 则是转为 Stylesheet，
 * 这个方法优先考虑为跨平台提供一个大家都可以理解的特征。
 *
 * 最初设想是基于二进制的数值进行处理的，但为了降解实际终端适用的难度，还是回退
 *
 * 返回的数据结构：
 *
 * - dir: bin 值，可做 map，以 `[Flex.col]` 为 key
 * - wrap: bin 值，可做 map，以 `[Flex.wrap]` 为 key。注意，wrap 可能有为 0 的情况，表示为未声明，如果做 map 处理，可以忽略
 * - justify 和 align: 下有 `content`, `item`, `self` 三个值，为 字符串 或 undefined ，undefined 表示未声明，可跳过处理
 * - gapX, gapY: 数字 或 字符串 或 null 或 undefined ，v == null 可跳过处理
 */
export const parseFlexProps = (
  props: FlexProps | undefined | null,
): FlexParsed | undefined => {
  if (props == null) return undefined;
  const { flow, justify, align } = props;
  return {
    ...parseFlow(flow),
    justify: parseCIS(justify),
    align  : parseCIS(align, CISMode.align),
  };
};
